---
title: 🔒 Authentication
url: /authentication
description: How to implement authentication in your Dart Frog application.
sidebar:
  order: 7
---

There are many different approaches, protocols, and services when tackling
authentication in a backend, which can all be affected by the business logic of
the application.

Because of this Dart Frog does not bundle any feature, helpers or resources for
authentication out of the box. This means that developers have full freedom to
implement server authentication in the best way that fits their needs.

Nevertheless, there are a few common patterns that can used in many different
approaches to give the developer a head start. For example, there is a package
called [`dart_frog_auth`](https://pub.dev/packages/dart_frog_auth), which makes
it easy for a simple authentication method to be implemented while also layering
the foundation for more advanced authentication. See below for more details:

## Dart Frog Auth

The authentication methods provided in `dart_frog_auth` use different HTTP
headers depending on the method. Basic and Bearer authentication use the
`Authorization` header, as defined in
[`General HTTP`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication),
while Cookie-based authentication uses the `Cookie` header, as defined in
[`HTTP Cookies`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies). The
package provides support for Basic, Bearer, and Cookie-based authentications,
which are common authentication methods used by many developers.

### Basic Authentication

Like its name infers, this is a basic authentication scheme that consists of the
client sending a user's credentials in the `Authorization` header. The
credentials should be concatenated by a colon and encoded in a base64 string.
The encoded credentials are then set in the header as follows:

```
Authorization: Basic TOKEN
```

Due to the credentials being sent encoded and not encrypted, this authentication
can be considered less secure, especially when used without HTTPS/TLS.

### Bearer Authentication

Similar to the basic authentication scheme, the bearer authentication scheme
sends a user's credentials to the header with a single token instead of a
username and password.

The bearer token format is up to the issuing authority server to define. It
commonly consists of an access token with encrypted information that the server
can validate.

The header is defined as follows:

```
Authorization: Bearer TOKEN
```

### Cookie-based Authentication

Cookie-based authentication stores user credentials in HTTP cookies that are
stored in a user's web browser and automatically sent with each subsequent
request. This method uses the `Cookie` header to transmit authentication
information.

The cookies typically contain session tokens or encrypted user data that the
server can validate to authenticate the user. Common implementations include
session cookies, JWT tokens stored in cookies, or secure HTTP-only cookies.

The header is defined as follows:

```
Cookie: session_id=abc123; auth_token=xyz789
```

## Usage

Both authentication schemes described above can be applied in a Dart Frog server
by adding middleware to the routes that needs to be secured.

Consider the following application:

```
lib/
  |- user_repository.dart
routes/
  |- admin/
  |    |- index.dart
  |- posts/
       |- index.dart
```

Routes under `posts` are public, so they don't require any kind of
authentication, while on `admin`, only authenticated users can access their
endpoints. It's worth noting that the `user_repository.dart` file under the
`lib` folder offers methods to authenticate users.

### Basic Method

To implement the basic authentication scheme on `admin` routes, a middleware
file should be created under the admin folder with the following content:

```dart
// routes/admin/_middleware.dart
import 'package:dart_frog/dart_frog.dart';
import 'package:dart_frog_auth/dart_frog_auth.dart';
import 'package:blog/user.dart';

Handler middleware(Handler handler) {
  final userRepository = ...;
  return handler
      .use(requestLogger())
      .use(
        basicAuthentication<User>(
          authenticator: (context, username, password) {
            final userRepository = context.read<UserRepository>();
            return userRepository.fetchFromCredentials(username, password);
          },
        ),
      );
}
```

The `authenticator` parameter must be a method that receives three positional
arguments (context, username and password) and returns a user if any is found
for those credentials, otherwise it should return null.

If a user is returned (authenticated), it will be set in the request context and
can be read by request handlers, for example:

```dart
import 'package:dart_frog/dart_frog.dart';
import 'package:blog/user.dart';

Response onRequest(RequestContext context) {
  final user = context.read<User>();
  return Response.json(body: {'user': user.id});
}
```

In the case of `null` being returned (unauthenticated), the middleware will
automatically send an unauthorized `401` in the response.

### Bearer Method

To implement the bearer authentication scheme on `admin` routes, the same logic
used for the basic method can be applied:

```dart
// routes/admin/_middleware.dart
import 'package:dart_frog/dart_frog.dart';
import 'package:dart_frog_auth/dart_frog_auth.dart';
import 'package:blog/user.dart';

Handler middleware(Handler handler) {
  final userRepository = ...;
  return handler
      .use(requestLogger())
      .use(
        bearerAuthentication<User>(
          authenticator: (context, token) {
            final userRepository = context.read<UserRepository>();
            return userRepository.fetchFromAccessToken(token);
          }
        ),
      );
}
```

The `authenticator` parameter must be a function that receives two positional
argument the context and the token sent on the authorization header and returns
a user if any is found for that token.

Again, just like in the basic method, if a user is returned, it will be set in
the request context and can be read on request handlers, for example:

```dart
import 'package:dart_frog/dart_frog.dart';
import 'package:blog/user.dart';

Response onRequest(RequestContext context) {
  final user = context.read<User>();
  return Response.json(body: {'user': user.id});
}
```

In the case of `null` being returned (unauthenticated), the middleware will
automatically send an unauthorized `401` in the response.

### Cookies Method

To implement cookie-based authentication, you can use the `cookieAuthentication`
middleware:

```dart
// routes/admin/_middleware.dart
import 'package:dart_frog/dart_frog.dart';
import 'package:dart_frog_auth/dart_frog_auth.dart';
import 'package:blog/user.dart';

Handler middleware(Handler handler) {
  final userRepository = ...;
  return handler
      .use(requestLogger())
      .use(
        cookieAuthentication<User>(
          authenticator: (context, cookies) {
            final userRepository = context.read<UserRepository>();
            return userRepository.fetchFromAccessCookies(cookies);
          }
        ),
      );
}
```

The `authenticator` parameter must be a function that receives two positional
argument the context and the cookies set in the cookie header and returns a user
if any is found for that token.

Just like in the basic and bearer methods, if a user is returned, it will be set
in the request context and can be read on request handlers, for example:

```dart
import 'package:dart_frog/dart_frog.dart';
import 'package:blog/user.dart';

Response onRequest(RequestContext context) {
  final user = context.read<User>();
  return Response.json(body: {'user': user.id});
}
```

In the case of `null` being returned (unauthenticated), the middleware will
automatically send an unauthorized `401` in the response.

### Public vs Private Routes

In many instances, developers will want to apply authentication to some routes,
while not to others.

One of those can be described by looking at implementing a basic RESTful CRUD
API. In order to make such an API that allows consumers to create, update,
delete, and get user information, the following list of routes will need to be
created:

- `POST /users`: Creates a user
- `PATCH /users/[id]`: Updates the user with the given id.
- `DELETE /users/[id]`: Deletes the user with the given id.
- `GET /users/[id]`: Returns the user with the given id.

Those endpoints can be translated to the following structure in a Dart Frog
backend:

```
routes/
  |- users/
      |- index.dart // Handles the POST
      |- [id].dart // Handles PATCH, DELETE and GET
      |- _middleware.dart
```

It would make sense for the `PATCH`, `DELETE`, and `GET` routes to be
authenticated ones, since only an authenticated user would be allowed to change
this information.

To accomplish that, we need the middleware to apply authentication to all routes
except `POST`.

Such behavior is possible with the use of the `applies` optional predicate:

```dart
Handler middleware(Handler handler) {
  final userRepository = UserRepository();

  return handler
      .use(requestLogger())
      .use(provider<UserRepository>((_) => userRepository))
      .use(
        basicAuthentication<User>(
          authenticator: (context, username, password) {
            final userRepository = context.read<UserRepository>();
            return userRepository.userFromCredentials(username, password);
          },
          applies: (RequestContext context) async =>
              context.request.method != HttpMethod.post,
        ),
      );
}
```

In the above example, only routes that are not `POST` will have authentication
checked.

### Optionally Authenticated Routes

In some cases, developers may want to allow a route to be accessed with or
without authentication, and they would like to return a different response based
on the authentication status.

To achieve this, we recommend the following approach:

```dart
// Create your user model
sealed class User {}

class AppUser extends User {
  final String id;
}

class AnonymousUser extends User {}
```

```dart
// Create your middleware
Handler middleware(Handler handler) {
  return handler.use(cookieAuthentication<User>(
    authenticator: (context, cookies) {
      final userRepository = context.read<UserRepository>();
      final user = userRepository.getUserFromCookies(cookies);
      return user != null ? AppUser(user.id) : AnonymousUser();
    },
  ));
}
```

```dart
// Use it in your handler
Response onRequest(RequestContext context) {
  final user = context.read<User>();
  return user is AppUser ? AuthenticatedResponse() : UnauthenticatedResponse();
}
```

### Authentication vs. Authorization

Both Authentication and authorization are related, but are different concepts
that are often confused.

Authentication is about WHO the user is, while authorization is about WHAT a
user can do.

These concepts are related since we need to know who the user is in order to
check if they can perform or not a given operation.

`dart_frog_auth` only solves the authentication part of the problem. To enforce
authorization, it is up to the developer to implement it manually, or use an
authorization issue system like OAuth2, for example.

In technical terms, a request should return `401` (Unauthorized) when
authentication fails and `403` (Forbidden) when authorization failed.

The following snippet shows how authorization can be manually checked in
`DELETE /users/[id]` route, where only the current logged user is allowed to
delete itself:

```dart
Future<Response> onRequest(RequestContext context, String id) async {
  return switch (context.request.method) {
    HttpMethod.delete => _deleteUser(context, id),
    _ => Future.value(Response(statusCode: HttpStatus.methodNotAllowed)),
  };
}

Future<Response> _deleteUser(RequestContext context, String id) async {
  // If there is no authenticated user, `dart_frog_auth` automatically
  // responds with a 401.

  final user = context.read<User>();
  if (user.id != id) {
    // If the current authenticated user, obtained via `context.read<User>` is
    // not the same of the one of the incoming request, a forbidden is returned!
    return Response(statusCode: HttpStatus.forbidden);
  }
  await context.read<UserRepository>().deleteUser(user.id);
  return Response(statusCode: HttpStatus.noContent);
}
```
